use super::Event;
use crate::Error;
use core::mem::MaybeUninit;
use core::ptr;
use hipool::{Boxed, MemPool, NullAlloc};

pub const POLLIN: u32 = libc::EPOLLIN as u32;
pub const POLLOUT: u32 = libc::EPOLLOUT as u32;
pub const POLLET: u32 = libc::EPOLLET as u32;
pub const POLLONESHOT: u32 = libc::EPOLLONESHOT as u32;

type Pool = &'static MemPool;

#[repr(C)]
pub struct Poll {
    fd: i32,
    events: Boxed<'static, [MaybeUninit<libc::epoll_event>], NullAlloc>,
}

unsafe impl Send for Poll {}

pub type BoxedPoll = Boxed<'static, Poll, NullAlloc>;

impl Poll {
    pub fn new_in(pool: Pool) -> Result<BoxedPoll, Error> {
        let events = Boxed::uninit_slice_in::<libc::epoll_event>(pool, EPOLL_EVENT_MAX)?;
        let uninit = Boxed::uninit_in::<Self>(pool)?;
        let fd = unsafe { libc::epoll_create1(libc::EPOLL_CLOEXEC) };
        if fd >= 0 {
            let poll = uninit.write(Self {
                fd,
                events: events.into(),
            });
            Ok(poll.into())
        } else {
            Err(Error::last_error())
        }
    }
}

impl Drop for Poll {
    fn drop(&mut self) {
        unsafe { libc::close(self.fd) };
    }
}

impl Poll {
    pub fn add_event(&self, fd: i32, events: u32, e: *const Event) -> Result<(), Error> {
        let mut event = libc::epoll_event {
            events,
            u64: e as u64,
        };
        let ret =
            unsafe { libc::epoll_ctl(self.fd, libc::EPOLL_CTL_ADD, fd, ptr::addr_of_mut!(event)) };
        if ret == 0 {
            Ok(())
        } else {
            Err(Error::last_error())
        }
    }

    pub fn mod_event(&self, fd: i32, events: u32, e: *const Event) -> Result<(), Error> {
        let mut event = libc::epoll_event {
            events,
            u64: e as u64,
        };
        let ret =
            unsafe { libc::epoll_ctl(self.fd, libc::EPOLL_CTL_MOD, fd, ptr::addr_of_mut!(event)) };
        if ret == 0 {
            Ok(())
        } else {
            Err(Error::last_error())
        }
    }

    pub fn del_event(&self, fd: i32) -> Result<(), Error> {
        let mut event = libc::epoll_event { events: 0, u64: 0 };
        let ret =
            unsafe { libc::epoll_ctl(self.fd, libc::EPOLL_CTL_DEL, fd, ptr::addr_of_mut!(event)) };
        if ret == 0 {
            Ok(())
        } else {
            Err(Error::last_error())
        }
    }

    pub fn wait<F>(&mut self, timeout: i32, mut f: F) -> Result<u32, Error>
    where
        F: FnMut(u32, *const Event),
    {
        let events = self.events.as_ptr().cast::<libc::epoll_event>();
        let cnt = unsafe { libc::epoll_wait(self.fd, events, self.events.len() as i32, timeout) };
        if cnt >= 0 {
            for event in &mut self.events[0..cnt as usize] {
                let event = unsafe { event.assume_init_ref() };
                let mut events = event.events & (POLLIN | POLLOUT);
                if events == 0 {
                    events = POLLIN | POLLOUT;
                }
                f(events, event.u64 as *const Event);
            }
            Ok(cnt as u32)
        } else {
            Err(Error::last_error())
        }
    }
}

const EPOLL_EVENT_MAX: usize = 512;
